﻿// SyntheticDataGenerator.Core.Sequence
//
// (c) 2011 Arthur Pitman
//
// Part of Market-Basket Synthetic Data Generator
// A C# adaptation of the IBM Quest Market-Basket Synthetic Data Generator
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// (Version 2) as published by the Free Software Foundation.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License (Version 2) along with this program; if not, write to 
// the Free Software Foundation, Inc., 
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// or see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SyntheticDataGenerator.Generators;
using System.IO;

namespace SyntheticDataGenerator.Core
{
	/// <summary>
	/// Represents a sequence
	/// </summary>
	public class Sequence
	{
		/// <summary>
		/// Source of randomness for sequences
		/// </summary>
		protected static UniformGenerator uniformGenerator = new UniformGenerator();  

		/// <summary>
		/// The id of the <see cref="Sequence"/>
		/// </summary>
		protected int id;

		/// <summary>
		/// (Expected) number of transactions in the <see cref="Sequence"/>
		/// </summary>
		protected int sequenceLength;

		/// <summary>
		/// avg. expected number of items in a transaction
		/// </summary>
		protected int transactionLength;

		/// <summary>
		/// Current number of items in the sequence
		/// </summary>
		protected int itemCount;

		/// <summary>
		/// The transactions that make up the sequence
		/// </summary>
		protected List<Transaction> transactionList;

		/// <summary>
		/// Initializes a new <see cref="Sequence"/> instance
		/// </summary>
		/// <param name="id"></param>
		/// <param name="patternSetIterator"></param>
		/// <param name="liPatternSet"></param>
		/// <param name="sequenceLength"></param>
		/// <param name="transactionLength"></param>
		public Sequence (int id, PatternSetIterator patternSetIterator, PatternSet liPatternSet, int sequenceLength, int transactionLength)
		{
			this.id = id;
			this.sequenceLength = sequenceLength;
			this.transactionLength = transactionLength;
			
			this.itemCount = 0;
			transactionList = new List<Transaction>();
			for (int i = 0; i < sequenceLength; i++)
				transactionList.Add(null);

			while (itemCount < sequenceLength * transactionLength)
			{
				// get a pattern
				Pattern pat = patternSetIterator.GetPattern();

				// try and add the pattern to the sequence, stop if full
				if (!AddPattern(pat, liPatternSet))
				{
					patternSetIterator.UnGetPattern();
					break;
				}
			}		   
		}

		/// <summary>
		/// Adds a <see cref="Pattern"/> to a <see cref="Transaction"/>
		/// </summary>
		/// <param name="pattern"></param>
		/// <param name="litsPatternSet"></param>
		/// <returns><c>true</c> if the <see cref="Pattern"/> fitted, otherwise <c>false</c></returns>
		protected bool AddPattern(Pattern pattern, PatternSet litsPatternSet)	
		{			
			// determine whether to corrupt transactions
			bool corrupt = (uniformGenerator.Next() > pattern.Confidence);				

			// corrupt the pattern by reducing its length;
			// the confidence for a pattern is decided at the time of pattern creation
			int patternLength = pattern.Items.Length;
			if (corrupt)
			{
				while ((patternLength > 0) && (uniformGenerator.Next() > pattern.Confidence))
					patternLength--;
			}
			if (patternLength == 0)	// no trans. left in sequence
				return true;

			var shuffleIndexes = uniformGenerator.ChooseRandomSubset(pattern.Items.Length, patternLength).ToArray();

			// calculate # of items in pattern
			int newItemCount = 0;
			foreach (var itemIndex in shuffleIndexes)
				newItemCount += litsPatternSet.GetPattern(pattern.Items[itemIndex]).Items.Length;

			// in half of the cases, we drop the pattern that won't fit
			if ((patternLength > sequenceLength) || (newItemCount + itemCount > sequenceLength * transactionLength))
			{
				if (uniformGenerator.Next() > 0.5)
					return false;
			}

			// ensure the list is long enough
			while (transactionList.Count < patternLength)
				transactionList.Add(null);
			
			int[] shuffleIndexes2 = null;
			if (sequenceLength > patternLength)
				shuffleIndexes2 = uniformGenerator.ChooseRandomSubset(sequenceLength, patternLength).ToArray();

			// add new sequence
			for (int i = 0; i < patternLength; i++)
			{
				int index = i;
				if (shuffleIndexes2 != null)
					index = shuffleIndexes2[i];

				if (transactionList[index] == null)
					transactionList[index] = new Transaction(transactionLength);
				int oldItemCount = transactionList[index].Items.Count;
				transactionList[index].AddPattern(litsPatternSet.GetPattern(pattern.Items[shuffleIndexes[i]]), corrupt);	

				// update item count
				itemCount += transactionList[index].Items.Count - oldItemCount;  
			}

			// success
			return true;
		}

		/// <summary>
		/// Writes the <see cref="Sequence"/> to the specified <see cref="TextWriter"/>
		/// </summary>
		/// <param name="writer"></param>
		public void Write(TextWriter writer)
		{
			for (int i = 0; i < sequenceLength; i++)
			{
				if (transactionList[i] != null)
					transactionList[i].Write(writer, id);
			}
		}

		/// <summary>
		/// Writes the <see cref="Sequence"/> to the specified <see cref="TextWriter"/> using a flat format
		/// e.g. 1, 3, 4; 3, 4, 8; 1, 2
		/// </summary>
		/// <param name="writer"></param>
		public void WriteFlat(TextWriter writer)
		{
			bool firstElement = true;
			foreach (var transaction in transactionList)
			{
				if (transaction != null && transaction.Items.Count > 0)
				{
					if (!firstElement)
						writer.Write("; ");

					bool firstItem = true;
					foreach (var item in transaction.Items)
					{
						if (!firstItem)
							writer.Write(", ");
						writer.Write(item);
						firstItem = false;
					}
					firstElement = false;
				}
			}
			writer.WriteLine();                     
		}	
	}
}
